/*
TP 1:

Versión inicial 0.8.0 10/10/2013 Gonzalo Prieto
Versión inicial 0.8.1 11/10/2013 Diego Luna
Versión inicial 0.8.1 13/10/2013 - (Correcciones menores.)

Versión entregada:
Versión 0.9.0 --/--/2013

Authors:

Gonzalo Prieto  <gonprieto_AT_gmail_DOT_com>
Ramiro Doi      <ramirodoi_AT_gmail_DOT_com>
Diego Luna      <diegorluna_AT_gmail_DOT_com>

*/


#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <strings.h>
#include <getopt.h>
#include <assert.h>

#include "globals.h"
#include "exiterror.h"
#include "fileproc.h"
#include "bubblesort.h"
#include "heapsort.h"


/*/////////////////////// Constantes del main - BEGIN - /////////////////////////////////*/
/*///////////////////////////////////////////////////////////////////////////////////////*/

#ifndef VERSION
#define VERSION "0.8.2"
#endif

#define STR_VERSION "TP1 66.20 version: %s\n"


/*
Command line options

tp1 [OPTIONS][file...]
-h, --help display this help and exit.
-V, --version display version information and exit.
-m, --bubble use the bubblesort algorithm.
-s, --heap use the heapsort algorithm.
*/

#define USAGE_TEXT			"\nUsage:\n\n"\
					"%s -h\n"\
					"%s -V\n"\
					"%s [OPTIONS][file...]\n\n"\
					"Options:\n\n"


#define HELP_TEXT			"\t-h, --help\t\tPrints usage information.\n"\
					"\t-V, --version\t\tPrints version information.\n"\
					"\t-o, --output\t\tPath to output file.\n"\
					"\t-m, --bubble\t\tUse the bubblesort algorithm.\n"\
					"\t-p, --heap\t\tUse the heapsort algorithm.\n\n"


#define EXAMP_TEXT			"Examples:\n\n"\
					"  %s -m file1 file2\n"\
					"  %s -p file\n"\
					"  %s file\n\n"


#define FILE_REDIR_STD			"-"
#define FILE_STD_OUT_NAME		"<Standard output>"
/*///////////////////////////////////////////////////////////////////////////////////////*/
/*///////////////////////// Constantes del main - END - /////////////////////////////////*/


/*/////////////////// Variables globales que se exportan - BEGIN - //////////////////////*/
/*///////////////////////////////////////////////////////////////////////////////////////*/

const char* pprocfilename = NULL;		/* Nombre del archivo de entrada que se está procesando (nombre mostrado). */

const char* pcoutputfilename = NULL;		/* Nombre del archivo de salida (nombre mostrado). */

comparefunc_t* compare_str = strcasecmp;	/* Puntero a la función de comparación de strings. */
/*/////////////////// Variables globales que se exportan - END - ////////////////////////*/
/*///////////////////////////////////////////////////////////////////////////////////////*/


/*/////////////////////// Variables globales del main - BEGIN - /////////////////////////*/
/*///////////////////////////////////////////////////////////////////////////////////////*/

static unsigned int uicount_files = 0;		/* Cantidad de archivos a procesar pasados. */

static char * const *  file_list;		/* Archivos a procesar. */

static const char * pcoutputfile = NULL;	/* Nombre del archivo de salida. */

static bool bbubblesort = false;		/* Usar el algoritmo bubblesort. */

static bool bheapsort = false;			/* Usar el algoritmo heapsort. */

static bool bspecoutput = false;		/* Se especificó el archivo de salida. */
/*///////////////////////////////////////////////////////////////////////////////////////*/
/*/////////////////////// Variables globales del main - END - ///////////////////////////*/


/*/////////////////////////////// Funciones del main - BEGIN - //////////////////////////*/
/*///////////////////////////////////////////////////////////////////////////////////////*/

static void clean_exit(const int istatus, const bool bshowerrormessage);	/* Termina el programa mostrando el estado de salida. */

static void parse_cmdline(int argc, char* const argv[]);			/* Parsea la línea de comando. */

static void do_usage(const char *pcexename, const int istatus);			/* Procesa la opción "-h". */
static void do_version(const char *pcexename);					/* Procesa la opción "-V". */
static void do_output(const char *pcexename, const char *pcargs);		/* Procesa la opción "-o". */
static void do_bubblesort(const char *pcexename);				/* Procesa la opción "-m". */
static void do_heapsort(const char *pcexename);					/* Procesa la opción "-p". */

/*/////////////////////////////// Funciones del main - END - ////////////////////////////*/
/*///////////////////////////////////////////////////////////////////////////////////////*/


/*
Muestra por stderr un mensaje de status (error) y termina el programa devolviendo
un valor correspondiente al status de salida.

1er parámetro: Status de salida.

2do parámetro: Indica si se debe o no mostrar el texto del error.

devuelve: -.
*/
static void clean_exit(const int istatus, const bool bshowerrormessage)
{
        if (bshowerrormessage)
                messageerror(istatus);				/* Convierto el error a texto y lo muestro. */

        exit(istatus);						/* Termino el programa devolviendo el código de error. */
}


/*
Procesa la línea de comandos.

1er parámetro: Cantidad de argumentos del programa.

2do parámetro: Puntero a los argumentos.


devuelve: -. (La función puede terminar el programa)
*/
static void parse_cmdline(int argc, char * const argv[])
{
        int ch;							/* Valor devuelto por la función getopt_long. */
        int opt_index = 0;


        struct option optionarr[6] =
        {
                {"version", no_argument, NULL, 'V'},		/* Versión del programa. */
                {"help", no_argument, NULL, 'h'},		/* Ayuda (uso del programa). */
                {"output", required_argument, NULL, 'o'},	/* Path to output file. */
                {"bubble", required_argument, NULL, 'm'},	/* Usar el algoritmo bubblesort. */
                {"heap", required_argument, NULL, 'p'},		/* Usar el algoritmo heapsort. */
                {NULL, no_argument, NULL, 0}
        };

        /*
        Llama repetidamente a la función getopt_long (devuelve el caracter que
        representa a la opción o EOF) hasta procesar todos
        los argumentos.
        */
        opterr = 0;						/* No quiero que getopt_long imprima errores. */
        opt_index = 0;						/* Inicializo opt_index (indice de opcion). */

        while ((ch = getopt_long(argc, argv, ":Vho:mp",
                                 optionarr, &opt_index)) != -1)
        {
                switch (ch)
                {
                case 'V':
                        do_version(argv[0]);
                        break;
                case 'h':
                        do_usage(argv[0], ERROR_SUCCESS);
                        break;
                case 'o':
                        do_output(argv[0], optarg);
                        break;
                case 'm':
                        do_bubblesort(argv[0]);
                        break;
                case 'p':
                        do_heapsort(argv[0]);
                        break;
                case '?':
                        do_usage(argv[0], ERROR_INVALID_COMMAND_LINE);
                case ':':
                        do_usage(argv[0], ERROR_MISSING_REQUIRED_PARAMETER);

                default:
                        do_usage(argv[0], ERROR_INVALID_COMMAND_LINE);
                }
        }

        /* obtengo los archivos de entrada que me pasaron. */
        /* La función getopt_long ya se encargó de dejarlos al final. */

        uicount_files = argc - optind;

        /* Verifico se haya pasado al menos un archivo a procesar. */
        if(uicount_files < 1)
                do_usage(argv[0], ERROR_BAD_INPUT_FILES_COUNT);

        file_list = &(argv[optind]);


        /* Verifico que parámetros deben tomar sus valores por default. */

        /* Si no se especificó ningún algoritmo de ordenamiento, se usa bubblesort. */
        if (!bbubblesort && !bheapsort)
        {
                bbubblesort = true;
                messageinfo(INFO_STR_DEFAULTING_ALGORITHM, INFO_STR_BUBBLE_SORT);
        }

        /* Si no se especificó archivo de salida, se usa la salida standard . */
        if (pcoutputfile == NULL)
        {
                pcoutputfile = FILE_REDIR_STD;
                messageinfo(INFO_STR_DEFAULTING_OUTPUT);
        }
}


/*
Imprime la ayuda de la linea de comandos
para el programa.

1er parámetro: Puntero al nombre del ejecutable.

2do parámetro: Puntero al parámetro de la opcion.


devuelve: -.
*/
static void do_usage(const char *pcexename, const int istatus)
{
        if (istatus != ERROR_SUCCESS)				/* Si no se llamo por invocar con -h o --help. */
                messageerror(istatus);

        fprintf(stderr, HELP_TEXT);

        fprintf(stderr, EXAMP_TEXT, pcexename, pcexename, pcexename);

        clean_exit(istatus, false);
}


/*
Imprime la version del programa.

1er parámetro: Puntero al nombre del ejecutable.


devuelve: -.
*/
static void do_version(const char *pcexename)
{
        (void) pcexename;

        fprintf(stderr, STR_VERSION, VERSION);
        clean_exit(ERROR_SUCCESS, false);
}


/*
Determina el archivo de salida.

1er parámetro: Puntero al nombre del ejecutable.

2do parámetro: Puntero al parámetro de la opcion.


devuelve: -.
*/
static void do_output(const char *pcexename, const char *pcargs)
{
        (void) pcexename;

        if (!pcargs || !strlen(pcargs))				/* Valido argumentos. */
                clean_exit(ERROR_NEED_ARGUMENTS, true);

        if(bspecoutput)						/* Opción repetida. */
                clean_exit(ERROR_ONLY_ONE_OUTPUT, true);

        pcoutputfile = pcargs;					/* Guardo el nombre del archivo de salida. */

        bspecoutput = true;
}


/*
Determina que se debe usar el algoritmo bubblesort.

1er parámetro: Puntero al nombre del ejecutable.


devuelve: -.
*/
static void do_bubblesort(const char *pcexename)
{
        (void) pcexename;

        if(bbubblesort)                                   	 /* Opción repetida. */
                clean_exit(ERROR_ONLY_ONE_BUBBLE, true);

        if(bheapsort)                                   	 /* Opción no permitida. */
                clean_exit(ERROR_ONLY_ONE_ALGORITHM, true);

        bbubblesort = true;
}


/*
Determina que se debe calcular el algoritmo heapsort.

1er parámetro: Puntero al nombre del ejecutable.


devuelve: -.
*/
static void do_heapsort(const char *pcexename)
{
        (void) pcexename;

        if(bheapsort)                                   	 /* Opción repetida. */
                clean_exit(ERROR_ONLY_ONE_HEAP, true);

        if(bbubblesort)                                   	 /* Opción no permitida. */
                clean_exit(ERROR_ONLY_ONE_ALGORITHM, true);

        bheapsort = true;
}


/*
Rutina principal del programa

1er parámetro: cantidad de argumentos.

2do parámetro: Puntero a los argumentos.

3er parámetro: Puntero a variables de entorno.


devuelve: código de error al sistema.
*/
int main(int argc, char * const argv[])
{
        FILE* fostream;

        bool bout_is_stdout;

        int ierror, i;

        char** word_list = NULL;
        int list_size, word_count = 0;

        /* Proceso la linea de comandos (en caso de errores fatales, acá termina la ejecución). */
        parse_cmdline(argc, argv);

        /* Determino si la salida a usar es stdout. */
        bout_is_stdout = strequal(pcoutputfile, FILE_REDIR_STD);

        /*
        Abro el archivo de salida sino es la salida standard.
        */
        if (bout_is_stdout)
        {
                /* Guardo un puntero al nombre del archivo de salida (para mensajes de error). */
                pcoutputfilename = FILE_STD_OUT_NAME;

                /* Redirijo mi salida a stdout. */
                fostream = stdout;
        }
        else
        {
                /* Guardo un puntero al nombre del archivo de salida (para mensajes de error). */
                pcoutputfilename = pcoutputfile;

                /* Abro el archivo en modo texto en solo escritura. */
                if (!(fostream = fopen(pcoutputfile, "w")))
                        clean_exit(ERROR_CANNOT_OPEN_OUTPUT_FILE, true);
        }

        /* Informo el comienzo del armado de la lista.*/
        messageinfo(INFO_STR_BUILDING_WORD_LIST);

        /* Armo la lista de palabras a partir de los archivos pasados por línea de comando. */
        ierror = make_word_list(uicount_files, file_list, &word_list, &word_count, &list_size);

        /* Si hubo algún error en el armado de la lista se termina acá la ejecución del programa.*/
        if (ierror != ERROR_SUCCESS)
                clean_exit(ierror, true);

        /* Informo que la lista se leyó correctamente.*/
        messageinfo(INFO_STR_DONE);

        /* Informo la cantidad de palabras a ordenar y el algoritmo que se seleccionó.*/
        messageinfo(INFO_STR_SORTING_N_WORDS, word_count, bbubblesort?INFO_STR_BUBBLE_SORT:INFO_STR_HEAP_SORT);

	/* Si hay palabras por ordenar. */
        if (word_count > 1)
        {
                /* Ordeno las palabras con el algoritmo indicado. */
                if (bbubblesort)
                        tp1_bubblesort(word_list, word_count);
                else
                        tp1_heapsort(word_list, word_count);

                /* Testeo del resultado del ordenamiento (Solo para debugging). */
#ifdef DEBUG
                assert (order_test(word_list,word_count));
#endif
        }

        /* Informo que la lista se ordenó correctamente.*/
        messageinfo(INFO_STR_DONE);

        /* Informo que se va a escribir la lista ordenada.*/
        messageinfo(INFO_STR_WRITING_SORTED_LIST);

        /* Imprimo la lista ordenada de palabras. */
        for (i = 0; i < word_count; i++)
        {
                if (fprintf(fostream, "%s", word_list[i]) < 0)
                        clean_exit(ERROR_WRITING_TO_OUTPUT, true);


                if (i < (word_count - 1))
                {
                        if (fprintf(fostream, " ") < 0)
                                clean_exit(ERROR_WRITING_TO_OUTPUT, true);

                }
        }

        /* Fuerzo el vaciado del buffer de salida. */
        fflush(fostream);

        /* Informo que la lista ordenada se escribió correctamente.*/
        messageinfo(INFO_STR_DONE);

        if (!bout_is_stdout)
                /* Cierro el archivo de salida. */
                fclose(fostream);

        /* Libero la memoria utilizada por la lista de palabras. */
        free_word_list (&word_list, &word_count, &list_size);

        /* El proceso se completó sin errores. */
        messageerror(ERROR_SUCCESS);

        return ERROR_SUCCESS;
}
